/*
 * @(#)Similarity.java
 *
 * (c) COPYRIGHT 1998-1999 MOTOROLA INC.
 * MOTOROLA CONFIDENTIAL PROPIETARY
 * MOTOROLA Advanced Technology and Software Operations
 *
 * REVISION HISTORY:
 * Author Date       CR Number         Brief Description
 * ------ ---------- ----------------- ------------------------------
 * wfo007 22/08/2008
 */

package br.edu.ufcg.splab.techniques.reduction.strategies;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import br.edu.ufcg.splab.generator.TestCase;
import br.edu.ufcg.splab.generator.TestSuite;
import br.edu.ufcg.splab.techniques.reduction.InterfaceReduction;
import br.edu.ufcg.splab.techniques.reduction.functions.choice.ChoiceFunction;
import br.edu.ufcg.splab.techniques.reduction.functions.similarity.DistanceFunction;
import br.edu.ufcg.splab.techniques.reduction.requirement.InterfaceRequirement;
import br.edu.ufcg.splab.techniques.reduction.requirement.RequirementTracer;


/**
 * A class that 
 * <pre>
 * 
 * CLASS:
 * 	
 *
 * RESPONSIBILITIES:
 * 	
 *
 * COLABORATORS: 
 *	
 *
 * USAGE:
 *	
 *
 * </pre>
 **/
public class ReductionDissimilarity  implements InterfaceReduction{
	
	private DistanceFunction function;
	private SimilarityMatrix similarityMatrix;
	private ChoiceFunction choice;

	/**
	 * The constructor of a Reduction Similarity Technique.
	 * The test suite will be generated in this constructor. 
	 * @param function The similarity function of the test cases.
	 */
	public ReductionDissimilarity(DistanceFunction function, ChoiceFunction choice)	{
		this.function = function;
		this.choice = choice;
		this.similarityMatrix = new SimilarityMatrix();
	}
	
	public TestSuite reduceTestSuite(TestSuite suite, List<RequirementTracer> tracer) {			
		return this.removeSimilarPaths(suite, tracer);
	} 

	public DistanceFunction getDistanceFunction(){
		return function;
	}
	
	public void setDistanceFunction(DistanceFunction function){
		this.function = function;
	}
		
    public ChoiceFunction getChoiceFunction() {
		return choice;
	}

	public void setChoiceFunction(ChoiceFunction choice) {
		this.choice = choice;
	}

	/**
	 * Remove the paths with high similarity while the test requirements are covered.
	 * 
	 * @param suite The complete test suite.
	 * @param tracers The ordered list of requirements of the complete test suite.
	 * @return The reduced test suite.
	 */
	protected TestSuite removeSimilarPaths(TestSuite suite, List<RequirementTracer> tracers) {
		List<TestCase> reducedTests = new ArrayList<TestCase>();		
		List<TestCase> tcs = new ArrayList<TestCase>();	

		for(RequirementTracer tracer: tracers){
			
			reducedTests.addAll(tcs);
			
			tcs = new ArrayList<TestCase>();
			
			List<TestCase>  tcs_temp = new ArrayList<TestCase>();
			TestSuite newTS = new TestSuite();
			for(TestCase tc: suite.getTestSuite()){
				if(!reducedTests.contains(tc)){
					tcs_temp.add(tc);
				}
			}
			
			newTS.addAll(tcs_temp);
			
			double[][] matrix = similarityMatrix.createMatrix(newTS, function);
			
			List<TestCase> testCases = new ArrayList<TestCase>(newTS.getTestSuite());
			List<TestCase> essencials = tracer.getEssentialTestCases();

			for (TestCase testCase : essencials) {
				int indexToRemove = testCases.indexOf(testCase);
				if(indexToRemove != -1){
					TestCase removed = testCases.remove(indexToRemove);
					matrix = similarityMatrix.removedLineAndColumn(matrix, indexToRemove);
					tcs.add(removed);
				}
			}
			
			
			List<TestCase> temp = new ArrayList<TestCase>(reducedTests);
			//Add essential test cases
			temp.addAll(tcs);
			
			// search the min value in the matrix
			while (!(this.satisfyRequirements(temp, tracer))) {
				double min = Double.MAX_VALUE;
				int indexLine = 0;
				int indexColumn = 0;
				// keep on the list "tie" the tie among the minimal value of the
				// matrix
				List<ElementMatrix> tie = new ArrayList<ElementMatrix>();
				// We use Matrix.size()-1: since the matrix is symmetrical,
				// the last line doesn't need to be verified.

				// this part of the code search for the minimal value in the matrix
				// the minimal value is always in position 0 of the "tie" list
				if (matrix.length == 1) {
					tie.clear();
					tie.add(new ElementMatrix(matrix[0][0], 0, 0));
				}

				for (int i = 0; i < matrix.length; i++) {
					for (int j = i + 1; j < matrix.length; j++) {
						double similarity = matrix[i][j];
						if (similarity < min && min >= 0) {
							min = similarity;
							indexLine = i;
							indexColumn = j;
							tie.clear();
							tie.add(new ElementMatrix(min, indexLine, indexColumn));
						} else if (similarity == min) {
							indexLine = i;
							indexColumn = j;
							tie.add(new ElementMatrix(min, indexLine, indexColumn));
						}
					}
				}
				
				// this part of the code searches between the tie elements (same
				// value of the matrix) which of them cover the maximum number
				// of not yet covered requirements.
				List<ElementMatrix> tieValue = new ArrayList<ElementMatrix>();
				ElementMatrix elementMatrixChoosen = null;

				// tie (list of ElementMatrix) contains all of minimum tied values
				int max = 0;
				for (ElementMatrix element : tie) {
					List<InterfaceRequirement> reqs = new ArrayList<InterfaceRequirement>(
							tracer.getRequirementsSatisfied(tcs));
					
					List<InterfaceRequirement> reqs2 = new ArrayList<InterfaceRequirement>(this.getCoveredRequirementsByTwoTestCases(element, tracer, suite ));

					//actual has the number of requirements not satisfied by tcx and tcy
					int actual = this.getTheNumberOfNotYetCoveredRequirementsWithTCs(reqs, reqs2);
				
					if (actual > max && max >= 0) {
						max = actual;
						tieValue.clear();
						tieValue.add(element);
					} else if (actual == max) {
						tieValue.add(element);

					}
				}

				Collections.shuffle(tieValue); // randomly permutes the list
				elementMatrixChoosen = tieValue.get(0);

				int firstChoose = 0;
				int secondChoose = 0;

				firstChoose = elementMatrixChoosen.getIndexLine();
				secondChoose = elementMatrixChoosen.getIndexColumn();
				
				// in this case the set of requirements of tc(firstChoose) contains
				// all test cases of tc(secondChoose)
				List<InterfaceRequirement> requirements1 = new ArrayList<InterfaceRequirement>();
				List<InterfaceRequirement> requirements2 = new ArrayList<InterfaceRequirement>();

				requirements1 = tracer.getRequirementsSatisfied(testCases.get(firstChoose));			
				requirements2 = tracer.getRequirementsSatisfied(testCases.get(secondChoose));

				// the same test case
				// OBS: The indexes used to remove the test cases are related to
				//		the matrix's similarity, where firstChoose will ALWAYS be
				//		smaller than secondChoose. Therefore if the algorithm for iteration
				//		is changed, the "testCases.remove(secondChoose - 1)" MUST BE updated.
				if ((this.containsRequirements(requirements1, requirements2))
						&& (this.containsRequirements(requirements2, requirements1))) {
					TestCase removed1 = testCases.remove(firstChoose);
					matrix = similarityMatrix.removedLineAndColumn(matrix, firstChoose);
					tcs.add(removed1);
				} else {
					// TCx contains all of requirements of TCy
					if (this.containsRequirements(requirements1, requirements2)) {
						TestCase removed1 = testCases.remove(firstChoose);					
						matrix = similarityMatrix.removedLineAndColumn(matrix, firstChoose);
						matrix = similarityMatrix.removedLineAndColumn(matrix, secondChoose - 1);
						tcs.add(removed1);
					} else {
						// TCy contains all of requirements of TCx					
						if (this.containsRequirements(requirements2, requirements1)) {
							matrix = similarityMatrix.removedLineAndColumn(matrix, firstChoose);
							TestCase removed2 = testCases.remove(secondChoose - 1);
							matrix = similarityMatrix.removedLineAndColumn(matrix, secondChoose - 1);
							tcs.add(removed2);
						} else {
							TestCase removed1;
							TestCase removed2;
							
							if (this.containsRequirements(tracer.getRequirementsSatisfied(tcs), requirements1)) {

								removed1 = testCases.remove(firstChoose);
								matrix = similarityMatrix.removedLineAndColumn(matrix, firstChoose);
								
							} else {
								removed1 = testCases.remove(firstChoose);
								matrix = similarityMatrix.removedLineAndColumn(matrix, firstChoose);
								tcs.add(removed1);							 
							}
							
							temp = new ArrayList<TestCase>(reducedTests);
							temp.addAll(tcs);
							if (!(satisfyRequirements(temp, tracer))) {
								if (this.containsRequirements(tracer
										.getRequirementsSatisfied(tcs), requirements2)) {

									removed2 = testCases.remove(secondChoose - 1);
									matrix = similarityMatrix.removedLineAndColumn(matrix, secondChoose - 1);
									
								} else {

									removed2 = testCases.remove(secondChoose - 1);
									matrix = similarityMatrix.removedLineAndColumn(matrix, secondChoose - 1);
									tcs.add(removed2);
								}
							}
						}
					}
				}
				
				temp = new ArrayList<TestCase>(reducedTests);
				temp.addAll(tcs);
				
//				List<InterfaceRequirement> reqSatisfied = new ArrayList<InterfaceRequirement>(tracer.getRequirementsSatisfied(temp));
//
//				// check which of the test cases should be throw, because all
//				// requirements covered by them, already covered.
//				System.out.println("matrix: " +  matrix.length + "   " + testCases.size());
//				for (int i = 0; i < testCases.size(); i++) {
//					//System.out.println("matrix: " +  matrix.length);
//					//System.out.println("i: " + i);
//					System.out.println(similarityMatrix.printMatrix(matrix));
//					List<InterfaceRequirement> requirementsTC = tracer.getRequirementsSatisfied(testCases.get(i));
//					if (this.containsRequirements(reqSatisfied, requirementsTC)) { // means that this test case is not necessary
//						matrix = similarityMatrix.removedLineAndColumn(matrix, i);
//					}
//				}
//				System.out.println("---------------------");
				
			}
			//end while
					
		}
		
		reducedTests.addAll(tcs);		
		
		TestSuite newTS = new TestSuite();
		newTS.addAll(reducedTests);
		
		return newTS;	
	}
		
	
	private int getTheNumberOfNotYetCoveredRequirementsWithTCs(List<InterfaceRequirement> alreadySatisfiedRequirements, List<InterfaceRequirement> satisfiedRequirementByTCs  ) {
		int i = 0;
		for (InterfaceRequirement requirement : satisfiedRequirementByTCs){
			if (!alreadySatisfiedRequirements.contains(requirement)){
				i++;
			}
		}
		return i;		
	}
	
	private boolean containsRequirements(List<InterfaceRequirement> reqTC1, List<InterfaceRequirement> reqTC2){	
		int sizeReq = reqTC2.size();
		int j = 0;
		for (int i=0; i < reqTC2.size();i++){			
			if ((reqTC1.contains(reqTC2.get(i)))){
				j = j+1;				
			}
		}
		return  sizeReq == j; 
	}

	/**
	 * Verify the covered requirements by two test cases
	 * @param indexTC1, indexTC2 The indexes of the test cases to be verified.
	 * @return A List of different requirements satisfied by two test cases  
	 */ 	
	private List<InterfaceRequirement> getCoveredRequirementsByTwoTestCases(ElementMatrix element, RequirementTracer requirements, TestSuite testCases){
		List<InterfaceRequirement> reqTC1 = new ArrayList<InterfaceRequirement>(requirements.getRequirementsSatisfied(testCases.get(element.getIndexLine())));
		List<InterfaceRequirement> reqTC2 = new ArrayList<InterfaceRequirement>(requirements.getRequirementsSatisfied(testCases.get(element.getIndexColumn())));
		
		for (int i=0; i < reqTC2.size();i++){
			if (!(reqTC1.contains(reqTC2.get(i)))){
				reqTC1.add(reqTC2.get(i));
			}
		}
		return reqTC1;
	} 
	
	/**
	 * Verify if all requirements are being satisfied by the given list of test cases.
	 * @param testCases The list of test case to be verified.
	 * @return A boolean value indicating if the given list of test cases are covering 100 
	 * percent of the LTS.
	 */ 
	
	private boolean satisfyRequirements(List<TestCase> testCases, RequirementTracer tracer) {
		boolean requirementCovered = false;			
		for (InterfaceRequirement requirement : tracer.getRequirements()) {	
			requirementCovered = false;			
			for(TestCase testCase: testCases){
				if(tracer.getTestCasesThatSatisfy(requirement) != null){
					if(tracer.getTestCasesThatSatisfy(requirement).contains(testCase)) {
						requirementCovered = true;
					}
				}
			}
			if(!requirementCovered){
				return false;
			}
		}		
		return true;
	}
	
    /**
     * Provide a string representation of the technique to be written.
     * @return The string representation of the technique.
     */
    public String toString() {
    	return "DSim_" + function.getFunctionName();
    }
   
}
